package com.pfsbuilder.algorithm;

import java.io.File;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Vector;

import javax.swing.JLabel;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import com.pfsbuilder.CommonLib;
import com.pfsbuilder.Library;
import com.pfsbuilder.PFSImageConstants;
import com.pfsbuilder.PFSSettingConstants;

public class PFSReadImageAlgorithm2 {
	public boolean shouldStop;
	int numOfFileRead;
	JLabel pInfoLabel;
	long readFileIndirectBlock_TimeStamp;
	RandomAccessFile br;
	// PIni ini;
	File file;
	public long rootDirectoryLink;
	public String partitionName;
	public String version;
	public int blockSize;
	public long numberOfFreeAddressBlock;

	public PFSReadImageAlgorithm2(String file) {
		this(new File(file));
	}

	public PFSReadImageAlgorithm2(File file) {
		try {
			this.file = file;
			// ini = new PIni("fuse.ini");
			br = new RandomAccessFile(file, "r");
			byte superBlock[] = new byte[PFSImageConstants.superBlockSize];
			br.seek(PFSImageConstants.partitionOffset);
			br.read(superBlock);

			if (!Library.checkMagicNumber(superBlock, 0)) {
				System.out.println("Erorr : Wrong fs format");
				br.close();
				return;
			} else {
				partitionName = new String(superBlock).substring(19, 519).trim();
				version = "2006";

				rootDirectoryLink = (long) (((long) (superBlock[520]) & 0xff) + (((long) (superBlock[521]) & 0xff) << 8) + (((long) (superBlock[522]) & 0xff) << 16)
						+ (((long) (superBlock[523]) & 0xff) << 24) + (((long) (superBlock[524]) & 0xff) << 32) + (((long) (superBlock[525]) & 0xff) << 40)
						+ (((long) (superBlock[526]) & 0xff) << 48) + (((long) (superBlock[527]) & 0xff) << 56));

				blockSize = (superBlock[544] + (superBlock[545] << 8)) * 1024;

				numberOfFreeAddressBlock = (long) (((long) (superBlock[546]) & 0xff) + (((long) (superBlock[547]) & 0xff) << 8) + (((long) (superBlock[548]) & 0xff) << 16)
						+ (((long) (superBlock[549]) & 0xff) << 24) + (((long) (superBlock[550]) & 0xff) << 32) + (((long) (superBlock[551]) & 0xff) << 40)
						+ (((long) (superBlock[552]) & 0xff) << 48) + (((long) (superBlock[553]) & 0xff) << 56));

				byte fab[] = new byte[(int) (PFSImageConstants.superBlockSize * numberOfFreeAddressBlock)];
				br.seek(PFSImageConstants.partitionOffset + PFSImageConstants.superBlockSize);
				br.read(fab);
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	public PFSDirectory readDirectory(long blockNumber) {
		PFSDirectory dir = new PFSDirectory();
		try {
			byte data[] = new byte[blockSize];
			long offset = PFSImageConstants.partitionOffset + PFSImageConstants.superBlockSize + blockNumber * blockSize;
			if (offset < 0) {
				return null;
			}
			br.seek(offset);
			br.read(data);
			// /////////////// directory name //////////////////////////
			dir.name = CommonLib.trimByteZero(new String(data).substring(3, 503));
			// showResult("Directory : " + currentPath + "/" + dirName_entry,
			// "");

			dir.numberOfFile = (long) (((long) (data[503]) & 0xff) + (((long) (data[504]) & 0xff) << 8) + (((long) (data[505]) & 0xff) << 16)
					+ (((long) (data[506]) & 0xff) << 24));
			dir.numberOfDirectory = (long) (((long) (data[507]) & 0xff) + (((long) (data[508]) & 0xff) << 8) + (((long) (data[509]) & 0xff) << 16)
					+ (((long) (data[510]) & 0xff) << 24));
			// /////////////// Permission //////////////////////////
			dir.permission = String.valueOf((char) data[511] & 0xff) + String.valueOf((char) data[512] & 0xff) + String.valueOf((char) data[513] & 0xff)
					+ String.valueOf((char) data[514] & 0xff) + String.valueOf((char) data[515] & 0xff) + String.valueOf((char) data[516] & 0xff)
					+ String.valueOf((char) data[517] & 0xff) + String.valueOf((char) data[518] & 0xff) + String.valueOf((char) data[519] & 0xff);

			// File selectedFile = new File(PFSSettingConstants.tempDirectory +
			// File.separator + currentPath + File.separator + dirName_entry);
			// if (data[511] == 'r') {
			// ini.setStringProperty(dir.name, "owner read", "true", null);
			// } else {
			// ini.setStringProperty(dir.name, "owner read", "false", null);
			// }
			// if (data[512] == 'w') {
			// ini.setStringProperty(dir.name, "owner write", "true", null);
			// } else {
			// ini.setStringProperty(dir.name, "owner write", "false", null);
			// }
			// if (data[513] == 'x') {
			// ini.setStringProperty(dir.name, "owner execute", "true", null);
			// } else {
			// ini.setStringProperty(dir.name, "owner execute", "false", null);
			// }
			// ini.save();

			dir.createTime = (long) ((long) ((data[520]) & 0xff) + ((long) ((data[521]) & 0xff) << 8) + ((long) ((data[522]) & 0xff) << 16) + ((long) ((data[523]) & 0xff) << 24)
					+ ((long) ((data[524]) & 0xff) << 32) + ((long) ((data[525]) & 0xff) << 40) + ((long) ((data[526]) & 0xff) << 48) + (((long) (data[527]) & 0xff) << 56));
			dir.lastModifiedTime = (long) (((long) (data[528]) & 0xff) + (((long) (data[529]) & 0xff) << 8) + (((long) (data[530]) & 0xff) << 16)
					+ (((long) (data[531]) & 0xff) << 24) + (((long) (data[532]) & 0xff) << 32) + (((long) (data[533]) & 0xff) << 40) + (((long) (data[534]) & 0xff) << 48)
					+ (((long) (data[535]) & 0xff) << 56));
			dir.directoryIndirectBlock = (long) (((long) (data[536]) & 0xff) + (((long) (data[537]) & 0xff) << 8) + (((long) (data[538]) & 0xff) << 16)
					+ (((long) (data[539]) & 0xff) << 24) + (((long) (data[540]) & 0xff) << 32) + (((long) (data[541]) & 0xff) << 40) + (((long) (data[542]) & 0xff) << 48)
					+ (((long) (data[543]) & 0xff) << 56));

			// create all the files
			// for (int x = 544; x < data.length - 9; x += 9) {
			// long link = (long) ((long) ((data[x + 1]) & 0xff) + ((long)
			// ((data[x + 2]) & 0xff) << 8) + ((long) ((data[x + 3]) & 0xff) <<
			// 16)
			// + ((long) ((data[x + 4]) & 0xff) << 24) + ((long) ((data[x + 5])
			// & 0xff) << 32) + ((long) ((data[x + 6]) & 0xff) << 40)
			// + ((long) ((data[x + 7]) & 0xff) << 48) + ((long) ((data[x + 8])
			// & 0xff) << 56));
			// if (link != 0) {
			// blockNumbers.add(new Entry(data[x] == 0, link));
			// }
			// }
			// if (directoryIndirectBlock != 0) {
			// if (currentPath == null) {
			// blockNumbers.addAll(readDIB(directoryIndirectBlock, ""));
			// } else {
			// blockNumbers.addAll(readDIB(directoryIndirectBlock, currentPath +
			// File.separator + dirName_entry));
			// }
			// }
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return dir;
	}

	public Vector<Entry> readDirectoryEntries(long blockNumber) {
		Vector<Entry> entries = new Vector<Entry>();
		try {
			byte data[] = new byte[blockSize];
			long offset = PFSImageConstants.partitionOffset + PFSImageConstants.superBlockSize + blockNumber * blockSize;
			if (offset < 0) {
				return null;
			}
			br.seek(offset);
			br.read(data);
			// /////////////// directory name //////////////////////////
			String dirName_entry = CommonLib.trimByteZero(new String(data).substring(3, 503));

			long numberOfFile = (long) (((long) (data[503]) & 0xff) + (((long) (data[504]) & 0xff) << 8) + (((long) (data[505]) & 0xff) << 16)
					+ (((long) (data[506]) & 0xff) << 24));
			long numberOfDirectory = (long) (((long) (data[507]) & 0xff) + (((long) (data[508]) & 0xff) << 8) + (((long) (data[509]) & 0xff) << 16)
					+ (((long) (data[510]) & 0xff) << 24));
			// /////////////// Permission //////////////////////////
			String permission = String.valueOf((char) data[511] & 0xff) + String.valueOf((char) data[512] & 0xff) + String.valueOf((char) data[513] & 0xff)
					+ String.valueOf((char) data[514] & 0xff) + String.valueOf((char) data[515] & 0xff) + String.valueOf((char) data[516] & 0xff)
					+ String.valueOf((char) data[517] & 0xff) + String.valueOf((char) data[518] & 0xff) + String.valueOf((char) data[519] & 0xff);

			// File selectedFile = new File(PFSSettingConstants.tempDirectory +
			// File.separator + currentPath + File.separator + dirName_entry);
			// if (data[511] == 'r') {
			// ini.setStringProperty(selectedFile.toString(), "owner read",
			// "true", null);
			// } else {
			// ini.setStringProperty(selectedFile.toString(), "owner read",
			// "false", null);
			// }
			// if (data[512] == 'w') {
			// ini.setStringProperty(selectedFile.toString(), "owner write",
			// "true", null);
			// } else {
			// ini.setStringProperty(selectedFile.toString(), "owner write",
			// "false", null);
			// }
			// if (data[513] == 'x') {
			// ini.setStringProperty(selectedFile.toString(), "owner execute",
			// "true", null);
			// } else {
			// ini.setStringProperty(selectedFile.toString(), "owner execute",
			// "false", null);
			// }
			// ini.save();

			long createTime = (long) ((long) ((data[520]) & 0xff) + ((long) ((data[521]) & 0xff) << 8) + ((long) ((data[522]) & 0xff) << 16) + ((long) ((data[523]) & 0xff) << 24)
					+ ((long) ((data[524]) & 0xff) << 32) + ((long) ((data[525]) & 0xff) << 40) + ((long) ((data[526]) & 0xff) << 48) + (((long) (data[527]) & 0xff) << 56));
			long lastModifiedTime = (long) (((long) (data[528]) & 0xff) + (((long) (data[529]) & 0xff) << 8) + (((long) (data[530]) & 0xff) << 16)
					+ (((long) (data[531]) & 0xff) << 24) + (((long) (data[532]) & 0xff) << 32) + (((long) (data[533]) & 0xff) << 40) + (((long) (data[534]) & 0xff) << 48)
					+ (((long) (data[535]) & 0xff) << 56));
			long directoryIndirectBlock = (long) (((long) (data[536]) & 0xff) + (((long) (data[537]) & 0xff) << 8) + (((long) (data[538]) & 0xff) << 16)
					+ (((long) (data[539]) & 0xff) << 24) + (((long) (data[540]) & 0xff) << 32) + (((long) (data[541]) & 0xff) << 40) + (((long) (data[542]) & 0xff) << 48)
					+ (((long) (data[543]) & 0xff) << 56));

			// create all the files
			for (int x = 544; x < data.length - 9; x += 9) {
				long link = (long) ((long) ((data[x + 1]) & 0xff) + ((long) ((data[x + 2]) & 0xff) << 8) + ((long) ((data[x + 3]) & 0xff) << 16)
						+ ((long) ((data[x + 4]) & 0xff) << 24) + ((long) ((data[x + 5]) & 0xff) << 32) + ((long) ((data[x + 6]) & 0xff) << 40)
						+ ((long) ((data[x + 7]) & 0xff) << 48) + ((long) ((data[x + 8]) & 0xff) << 56));
				if (link != 0) {
					entries.add(new Entry(data[x] == 0, link));
				}
			}
			if (directoryIndirectBlock != 0) {
				entries.addAll(readDIB(directoryIndirectBlock));
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return entries;
	}

	private Vector<Entry> readDIB(long blockNumber) {
		Vector<Entry> blockNumbers = new Vector<Entry>();
		try {
			showResult("DIB", "block number = " + blockNumber);

			byte data[] = new byte[blockSize];
			br.seek(PFSImageConstants.partitionOffset + PFSImageConstants.superBlockSize + blockNumber * blockSize);
			br.read(data);

			for (int x = 3; x < data.length - 9; x += 9) {
				long link = (long) (((long) (data[x + 1]) & 0xff) + (((long) (data[x + 2]) & 0xff) << 8) + (((long) (data[x + 3]) & 0xff) << 16)
						+ (((long) (data[x + 4]) & 0xff) << 24) + (((long) (data[x + 5]) & 0xff) << 32) + (((long) (data[x + 6]) & 0xff) << 40)
						+ (((long) (data[x + 7]) & 0xff) << 48) + (((long) (data[x + 8]) & 0xff) << 56));
				if (link != 0) {
					// if (data[x] == 0) {
					// readFile(link, currentPath);
					// } else {
					// readDirectory(link, currentPath);
					// }
					blockNumbers.add(new Entry(data[x] == 0, link));
				}
			}
			long directoryIndirectBlock = (long) (((long) (data[PFSSettingConstants.blockSize - 8]) & 0xff) + (((long) (data[PFSSettingConstants.blockSize - 7]) & 0xff) << 8)
					+ (((long) (data[PFSSettingConstants.blockSize - 6]) & 0xff) << 16) + (((long) (data[PFSSettingConstants.blockSize - 5]) & 0xff) << 24)
					+ (((long) (data[PFSSettingConstants.blockSize - 4]) & 0xff) << 32) + (((long) (data[PFSSettingConstants.blockSize - 3]) & 0xff) << 40)
					+ (((long) (data[PFSSettingConstants.blockSize - 2]) & 0xff) << 48) + (((long) (data[PFSSettingConstants.blockSize - 1]) & 0xff) << 56));

			if (directoryIndirectBlock != 0) {
				blockNumbers.addAll(readDIB(directoryIndirectBlock));
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			if (com.pfsbuilder.Global.stopWhenException) {
				System.exit(0);
			}
		}
		return blockNumbers;
	}

	public String readFileContent(long blockNumber) {
		PFSFile file = readFile(blockNumber);
		try {
			return new String(file.contents, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		}
	}

	public long getFileLength(long blockNumber) {
		PFSFile file = readFile(blockNumber);
		return file.size;
	}

	public PFSFile readFile(long blockNumber) {
		PFSFile file = new PFSFile();
		try {
			// eadFileIndirectBlock_TimeStamp = new Date().getTime();

			byte data[] = new byte[blockSize];
			br.seek(PFSImageConstants.partitionOffset + PFSImageConstants.superBlockSize + blockNumber * blockSize);
			br.read(data);

			file.name = CommonLib.trimByteZero(new String(data).substring(4, 504));

			file.permission = String.valueOf((char) data[504] & 0xff) + String.valueOf((char) data[505] & 0xff) + String.valueOf((char) data[506] & 0xff)
					+ String.valueOf((char) data[507] & 0xff) + String.valueOf((char) data[508] & 0xff) + String.valueOf((char) data[509] & 0xff)
					+ String.valueOf((char) data[510] & 0xff) + String.valueOf((char) data[511] & 0xff) + String.valueOf((char) data[512] & 0xff);
			file.createTime = (long) (((long) (data[513]) & 0xff) + (((long) (data[514]) & 0xff) << 8) + (((long) (data[515]) & 0xff) << 16) + ((long) ((data[516]) & 0xff) << 24)
					+ (((long) (data[517]) & 0xff) << 32) + (((long) (data[518]) & 0xff) << 40) + (((long) (data[519]) & 0xff) << 48) + (((long) (data[520]) & 0xff) << 56));
			file.lastModifiedTime = (long) (((long) (data[521]) & 0xff) + (((long) (data[522]) & 0xff) << 8) + (((long) (data[523]) & 0xff) << 16)
					+ (((long) (data[524]) & 0xff) << 24) + (((long) (data[525]) & 0xff) << 32) + ((long) ((data[526]) & 0xff) << 40) + (((long) (data[527]) & 0xff) << 48)
					+ (((long) (data[528]) & 0xff) << 56));
			file.size = (long) (((long) (data[529]) & 0xff) + (((long) (data[530]) & 0xff) << 8) + (((long) (data[531]) & 0xff) << 16) + (((long) (data[532]) & 0xff) << 24)
					+ (((long) (data[533]) & 0xff) << 32) + (((long) (data[534]) & 0xff) << 40) + (((long) (data[535]) & 0xff) << 48) + (((long) (data[536]) & 0xff) << 56));
			file.contents = new byte[(int) file.size];
			// showResult("File" + (numOfFileRead + 1) + ") " + file.filename +
			// " (" + CommonLib.convertFilesize(file.fileSize) + "," +
			// blockNumber + ")", "");

			if (file.size < 0) {
				System.out.println("File size error");
				return file;
			}
			// /////////////// fibLink //////////////////////////
			long fibLink = (long) (((long) (data[537]) & 0xff) + (((long) (data[538]) & 0xff) << 8) + (((long) (data[539]) & 0xff) << 16) + (((long) (data[540]) & 0xff) << 24)
					+ (((long) (data[541]) & 0xff) << 32) + (((long) (data[542]) & 0xff) << 40) + (((long) (data[543]) & 0xff) << 48) + (((long) (data[544]) & 0xff) << 56));
			// /////////////// links //////////////////////////

			int offset = 0;
			for (int x = 545; x <= data.length - 8; x += 8) {
				long link = (long) (((long) (data[x]) & 0xff) + (((long) (data[x + 1]) & 0xff) << 8) + (((long) (data[x + 2]) & 0xff) << 16) + (((long) (data[x + 3]) & 0xff) << 24)
						+ (((long) (data[x + 4]) & 0xff) << 32) + (((long) (data[x + 5]) & 0xff) << 40) + (((long) (data[x + 6]) & 0xff) << 48)
						+ (((long) (data[x + 7]) & 0xff) << 56));

				if (link != 0) {
					offset += readFileContent(link, file, offset);
					// CommonLib.printFilesize(offset, file.fileSize);
				}
			}

			if (fibLink != 0) {
				readFileIndirectBlock(fibLink, file, offset);
			}
			numOfFileRead++;
		} catch (Exception ex) {
			ex.printStackTrace();
			if (com.pfsbuilder.Global.stopWhenException) {
				System.exit(0);
			}
		}
		return file;
	}

	public long getBlockNo(String path) {
		return getBlockNo(path, rootDirectoryLink);
	}

	public long getBlockNo(String path, long blockNo) {
		if (path.equals("/")) {
			return rootDirectoryLink;
		}
		if (path.charAt(0) == '/') {
			path = path.substring(1);
		}
		String paths[] = path.split("/");
		String current = paths[0];

		Vector<Entry> entries = readDirectoryEntries(blockNo);
		for (Entry entry : entries) {
			if (entry.isFile) {
				PFSFile file = readFile(entry.blockNo);
				if (file.name.equals(current)) {
					if (paths.length > 1) {
						System.out.println("Cant find file/dir");
						return -1;
					} else {
						return entry.blockNo;
					}
				}
			} else {
				PFSDirectory dir = readDirectory(entry.blockNo);
				if (dir.name.equals(current)) {
					if (paths.length > 1) {
						String str = StringUtils.join(ArrayUtils.remove(paths, 0), "/");
						return getBlockNo(str, entry.blockNo);
					} else {
						return entry.blockNo;
					}
				}
			}
		}
		return -1;
	}

	public boolean isFile(String path) {
		return isFile(path, rootDirectoryLink);
	}

	public boolean isFile(String path, long blockNo) {
		if (path.equals("/")) {
			return false;
		}
		if (path.charAt(0) == '/') {
			path = path.substring(1);
		}
		String paths[] = path.split("/");
		String current = paths[0];

		Vector<Entry> entries = readDirectoryEntries(blockNo);
		for (Entry entry : entries) {
			if (entry.isFile) {
				PFSFile file = readFile(entry.blockNo);
				if (file.name.equals(current)) {
					return true;
				}
			} else {
				PFSDirectory dir = readDirectory(entry.blockNo);
				if (dir.name.equals(current)) {
					if (paths.length > 1) {
						String str = StringUtils.join(ArrayUtils.remove(paths, 0), "/");
						return isFile(str, entry.blockNo);
					} else {
						return false;
					}
				}
			}
		}
		return true;
	}

	private void readFileIndirectBlock(long blockNumber, PFSFile file, long offset) {
		try {
			byte data[] = new byte[blockSize];
			br.seek(PFSImageConstants.partitionOffset + PFSImageConstants.superBlockSize + blockNumber * blockSize);
			br.read(data);

			if (data[0] == 'F' && data[1] == 'I' && data[2] == 'B') {
				for (int x = 3; x <= PFSSettingConstants.blockSize - 8 - 8; x += 8) {
					long link = (long) (((byte) (data[x]) & 0xff) + (((byte) (data[x + 1]) & 0xff) << 8) + (((byte) (data[x + 2]) & 0xff) << 16)
							+ (((byte) (data[x + 3]) & 0xff) << 24) + (((byte) (data[x + 4]) & 0xff) << 32) + (((byte) (data[x + 5]) & 0xff) << 40)
							+ (((byte) (data[x + 6]) & 0xff) << 48) + (((byte) (data[x + 7]) & 0xff) << 56));
					if (link == 0) {
						break;
					}
					offset += readFileContent(link, file, offset);
					CommonLib.printFilesize(offset, file.size);
					System.out.print(String.format("%.2f", ((float) offset / (new Date().getTime() - readFileIndirectBlock_TimeStamp)) / 1024) + "MB/s    ");
				}
				if (offset < file.size) {
					long fibLink = (long) (((long) (data[PFSSettingConstants.blockSize - 8]) & 0xff) + (((long) (data[PFSSettingConstants.blockSize - 7]) & 0xff) << 8)
							+ (((long) (data[PFSSettingConstants.blockSize - 6]) & 0xff) << 16) + (((long) (data[PFSSettingConstants.blockSize - 5]) & 0xff) << 24)
							+ (((long) (data[PFSSettingConstants.blockSize - 4]) & 0xff) << 32) + (((long) (data[PFSSettingConstants.blockSize - 3]) & 0xff) << 40)
							+ (((long) (data[PFSSettingConstants.blockSize - 2]) & 0xff) << 48) + (((long) (data[PFSSettingConstants.blockSize - 1]) & 0xff) << 56));
					if (fibLink != 0) {
						readFileIndirectBlock(fibLink, file, offset);
					}
				}
			} else {
				showResult("Error", "FIB header incorrect");
				return;
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			if (com.pfsbuilder.Global.stopWhenException) {
				System.exit(0);
			}
		}
	}

	private long readFileContent(long blockNumber, PFSFile file, long offset) {
		try {
			byte data[] = new byte[blockSize];
			br.seek(PFSImageConstants.partitionOffset + PFSImageConstants.superBlockSize + blockNumber * blockSize);
			br.read(data);

			// RandomAccessFile bw = new RandomAccessFile(filePath, "rw");
			// bw.seek(offset);
			long len;
			if (file.size - offset > blockSize) {
				len = data.length;
				// bw.write(data);
				System.arraycopy(data, 0, file.contents, (int) offset, (int) len);
			} else {
				len = file.size - offset;
				// bw.write(data, 0, (int) (fileSize - offset));
				System.arraycopy(data, 0, file.contents, (int) offset, (int) len);
			}
			// bw.close();
			return len;
		} catch (Exception ex) {
			System.out.println(offset + "/" + file.size);
			ex.printStackTrace();
			if (com.pfsbuilder.Global.stopWhenException) {
				System.exit(0);
			}
			return 0;
		}
	}

	private void showResult(String columneOneString, String columneTwoString) {
		System.out.println(columneOneString + " " + columneTwoString);
	}

}
